/** @class recoveryLog
 recoveryLog - interface to recovery infrastructure
 
 $Id#
 Versioning: a.b.c a is a major release, b represents changes or new features, c represents bug fixes. 
 @version 1.0.0   06/10/2011    Gerhardus Muller     Script created

 @note

 @todo
 
 @bug

 Copyright Gerhardus Muller
 */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>

#include "message/tMessage.h"
#include "src/recoveryLog.h"
#include "utils/utils.h"

const char *const recoveryLog::recoveryFilebase = "recovery.log";
const char *const recoveryLog::recoveryDirbase = "recovery";
const char *const recoveryLog::logDirbase = "logs";
int recoveryLog::seq = 0;
int recoveryLog::countRecoveryLines = 0;
logger recoveryLog::staticLogger = logger( "recoveryS", loggerDefs::MIDLEVEL );
logger* recoveryLog::pStaticLogger = &recoveryLog::staticLogger;

/**
 * Construction - throws if file open fails or recovery dir does not exist (or wrong permissions)
 * @param baseDir of the server
 * @param bRotate - true to rotate before opening
 * @exception on failure to open log or recovery directory
 * **/
recoveryLog::recoveryLog( const char* baseDir )
  : object( "recoveryLog" )
{
  countRecoveryLines = 0;
  recoveryDir = baseDir;
  if( recoveryDir[recoveryDir.length()-1] != '/' ) recoveryDir += "/";
  recoveryDir += recoveryDirbase;
  recoveryDir += "/";
  recoveryLogname = baseDir;
  if( recoveryLogname[recoveryLogname.length()-1] != '/' ) recoveryLogname += "/";
  logDir = recoveryLogname;
  recoveryLogname += recoveryFilebase;

  log.info( log.LOGALWAYS, "opening recovery log %s, recovery dir '%s'", recoveryLogname.c_str(), recoveryDir.c_str() );
  ofs.open( recoveryLogname.c_str(), std::ofstream::app );
  if( !ofs.good() )
    throw Exception( log, log.ERROR, "recoveryLog: failed to open file %s", recoveryLogname.c_str() );

  // test the recovery dir - create a file and write to it
  std::string testName;
  testName = recoveryDir +  "testfile.txt";
  std::ofstream testStream( testName.c_str() );
  testStream << "test message to make sure the recovery directory is available\n";
  testStream.flush();
  if( !testStream.good() )
  {
    ofs.close();
    throw Exception( log, log.ERROR, "failed to create test file %s in directory %s", testName.c_str(), recoveryDir.c_str() );
  }
  testStream.close();
  log.setAutoTimestamp( false );
}	// recoveryLog

/**
 * destruction
 * **/
recoveryLog::~recoveryLog()
{
  ofs.close( );
}	// ~recoveryLog

/**
 * reopen - typically in response to a logrotate signal
 * @exception on failure to re-open log
 * **/
void recoveryLog::reOpen( )
{
  ofs.close( );
  ofs.open( recoveryLogname.c_str(), std::ofstream::app );
  if( !ofs.good() )
    throw Exception( log, log.ERROR, "reOpen: failed to open file:'%s' - %s", recoveryLogname.c_str(), strerror(errno) );
  else
    log.info( log.LOGALWAYS, "reOpen log %s", recoveryLogname.c_str() );
} // reOpen

/**
 * @return pointer to a string describing the state of the recoveryLog.  The string has an unspecified lifetime and is not multi-thread safe
 * **/
std::string recoveryLog::toString( )
{
  std::ostringstream oss;
  oss << typeid(*this).name() << " fn:" << recoveryLogname;
	return oss.str();
}	// toString

/**
 * writes the event to the recovery log - that is both the list and the logfile
 * @param theEvent to log
 * @param from - name of the object owning the event
 * @param to - name of the destination object
 * **/
void recoveryLog::writeEntry( tMessage* theEvent, const char* error, const char* from, const char* to )
{
  if( from == NULL ) from = "unknown";
  if( to == NULL ) to = "unknown";
  if( error == NULL ) error = "unknown";

  // create serialisation file for event
  char filename[1024];
  snprintf( filename, 1023, "%sr%06d_XXXXXX", recoveryDir.c_str(),seq );
  int fd = mkstemp( filename );
  int numBytesWritten = -1;

  // serialise event
  if( fd > -1 )
  {
    numBytesWritten = theEvent->serialiseToFile( fd );
    if( numBytesWritten == -1 )
      log.error( "writeEntry failed to write to the serialisation file %s err %s", filename, strerror( errno ) );
    if( close( fd ) != 0 )
      log.error( "writeEntry failed to close file %s err %s", filename, strerror( errno ) );
    if( chmod( filename, S_IRUSR|S_IWUSR|S_IRGRP ) != 0 )
      log.error( "writeEntry chmod failed on file %s err %s", filename, strerror( errno ) );
  } // if
  else
    log.error( "writeEntry failed to create serialisation file %s err %s", filename, strerror( errno ) );
  const char* result = (numBytesWritten==-1) ? "ERR" : "SUCC";

  // use the event's log timestamp if available
  std::string tt = theEvent->getTraceTimestamp( );
  if( !tt.empty() )
    log.setTimestamp( tt.c_str() );

  // retrieve the queue
  std::string queue = theEvent->getDestQueue();

  // write record to recovery log
  // result, date, time in seconds, error, from, to, queue, event class, serialised event filename, log timestamp, event string
  int now = time( NULL );
  ofs << result << "," << utils::timeToString( now ) << "," << now << "," << error << "," << from << "," << to << "," << queue << "," << theEvent->typeToString() << "," << filename << "," << log.getTimestamp() << "," << theEvent->toString() << std::endl;

  // log the event
  log.info( log.LOGALWAYS, "writeEntry %s from '%s' to '%s' name '%s' queue '%s' reason '%s' fn '%s' bytes %d", result, from, to, theEvent->typeToString(), queue.c_str(), error, filename, numBytesWritten );
  countRecoveryLines++;
  seq++;
} // writeEntry
